package top.healthylife.gateway.platform.bj.api.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import top.healthylife.basics.utils.exception.BusinessException;
import top.healthylife.basics.utils.utils.RedisUtil;
import top.healthylife.basics.utils.utils.SystemThreadLocal;
import top.healthylife.gateway.core.util.SystemConfigUtils;
import top.healthylife.gateway.dto.request.req.ApiReqDto;
import top.healthylife.gateway.platform.bj.api.config.SystemConfig;
import top.healthylife.gateway.platform.bj.dto.basedto.req.SecurityType;
import top.heathylife.gateway.common.cons.SystemCons;
import top.heathylife.gateway.common.po.RecordRequestInfo;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @author : GaoZeXi
 * @date 2021/7/14 13:54
 */
@UtilityClass
@Slf4j
public class BjHttpUtil {

    /**
     * 环境类型
     */
    public enum EnvType {
        /**
         * 测试环境
         */
        SIT,
        /**
         * 对接环境
         */
        PRE_PRO,
        /**
         * 生产环境
         */
        PRO
    }

    /**
     * 响应码
     */
    public static Map<String, String> respCodeMap = new HashMap<>();

    static final String LOGIN_URL = "/api/login.do";
    //----------------------生产环境-------------------------------
    /**
     * 登录域名
     */
    static final String PRO_DOMAIN_LOGIN = "https://sso.icekredit.com";
    /**
     * 服务域名
     */
    static final String PRO_DOMAIN_API = "https://api.icekredit.com";

    //----------------------对接环境-------------------------------
    /**
     * 登录域名
     */
    static final String PRE_PRO_DOMAIN_LOGIN = "https://sso-ci.icekredit.com";
    /**
     * 服务域名
     */
    static final String PRE_PRO_DOMAIN_API = "https://api-ci.icekredit.com";

    //----------------------测试环境-------------------------------
    /**
     * 登录域名
     */
    static final String SIT_DOMAIN_LOGIN = "https://sso-ci-mock.icekredit.com";
    /**
     * 服务域名
     */
    static final String SIT_DOMAIN_API = "https://api-ci-mock.icekredit.com";

    static final String TOKEN_KEY_PREFIX = "interface:api:bj:token:";


    static {
        respCodeMap.put("00", "success 请求成功");
        respCodeMap.put("02", "授权服务异常");
        respCodeMap.put("10000001", "服务处理异常，请联系客服");
        respCodeMap.put("10000429", "请求过于频繁");
        respCodeMap.put("10000400", "无效的请求：{0}");
        respCodeMap.put("10000401", "未授权");
        respCodeMap.put("10000404", "找不到请求资源");
        respCodeMap.put("10000415", "Content-Type 不支持：{0}");
        respCodeMap.put("10000405", "请求方法不支持：{0} ");
        respCodeMap.put("200002", "用户名或者密码错误");
        respCodeMap.put("200005", "token_id 无效：{0}");
        respCodeMap.put("200006", "客户端 IP 地址已改变");
        respCodeMap.put("200007", "没有权限访问该资源");
        respCodeMap.put("200008", "账户被锁定，请联系客服");
        respCodeMap.put("200009", "账户已过期，请联系客服");
        respCodeMap.put("200020", "验证码错误");
        respCodeMap.put("200010", "授权服务不可用，请联系客服");
        respCodeMap.put("20100102", "请求参数缺失");
        respCodeMap.put("20100103", "请求参数错误");
        respCodeMap.put("20100200", "该产品已停止提供服务");
        respCodeMap.put("20100201", "服务未配置，请联系客服");
        respCodeMap.put("20100202", "服务次数不足，请联系客服");
        respCodeMap.put("20100300", "获取数据错误，请联系客服");
        respCodeMap.put("20100301", "密文数据无法匹配");
        respCodeMap.put("20100302", "数据编码类型不支持");
    }

    public static final String SUCCESS = "00";
    public static final String CODE = "response_code";
    public static final String MSG = "response_msg";
    private static final Integer RETRY = 3;

    /**
     * 获取当前环境的登录地址或 服务访问地址
     *
     * @param env 当前环境
     * @return 登录地址
     */
    public static String getCurrentEnvLoginUrl(EnvType env) {
        switch (env) {
            case SIT:
                return SIT_DOMAIN_LOGIN + LOGIN_URL;
            case PRE_PRO:
                return PRE_PRO_DOMAIN_LOGIN + LOGIN_URL;
            case PRO:
                return PRO_DOMAIN_LOGIN + LOGIN_URL;
            default:
                throw new BusinessException("获取环境信息错误,期望在[SIT,PRE_PRO,PRO],实际是{}", env);
        }
    }

    /**
     * 获取当前环境的接口访问地址
     *
     * @param env 当前环境
     * @return 接口访问地址
     */
    static String getCurrentEnvApiUrl(EnvType env, String apiUrl) {
        switch (env) {
            case SIT:
                return SIT_DOMAIN_API + apiUrl;
            case PRE_PRO:
                return PRE_PRO_DOMAIN_API + apiUrl;
            case PRO:
                return PRO_DOMAIN_API + apiUrl;
            default:
                throw new BusinessException("获取环境信息错误,期望在[SIT,PRE_PRO,PRO],实际是{}", env);
        }
    }

    /**
     * 请求接口
     *
     * @param apiReqDto 入参
     * @param apiUrl    接口地址
     * @param cls       出参cls
     * @param <T>       出参cls
     * @return T 出参对象
     */
    public static <T> T httpRequest(ApiReqDto apiReqDto, String apiUrl, Class<T> cls) {
        //1.获取账号信息
        SystemConfig systemConfig = systemConfigInfo(apiReqDto);
        //2.登录
        getTokenId(systemConfig, 1);
        //3.请求接口
        return getApiData(apiReqDto, systemConfig, apiUrl, cls);
    }

    /**
     * 访问日志输出和配置获取
     *
     * @param apiReqDto 入参
     * @return 账号信息
     */
    public static SystemConfig systemConfigInfo(ApiReqDto apiReqDto) {
        //可直接通过工具类获取这个对象
        SystemConfig o = (SystemConfig) SystemConfigUtils.get();
        log.info("收到请求参数为:{}", JSONObject.toJSONString(apiReqDto));
        log.info("账号信息为:{}", JSONObject.toJSONString(o));
        return o;
    }

    /**
     * 登录 获取token
     *
     * @param systemConfig 集奥的账户 tokenId 未获取
     * @param i            重试次数
     */
    public static void getTokenId(SystemConfig systemConfig, int i) {
        EnvType env = systemConfig.getEnv();
        String merchantName = systemConfig.getMerchantName();
        String merchantPwd = systemConfig.getMerchantPwd();
        String encryptPwd = SecureUtil.md5(merchantPwd);
        String tokeKey = TOKEN_KEY_PREFIX + merchantName;
        //查看token是否存在
        if (RedisUtil.exists(tokeKey)) {
            String tokenId = RedisUtil.get(tokeKey).toString();
            systemConfig.setTokenId(tokenId);
            return;
        }
        String currentEnvLoginUrl = "";
        try {
            currentEnvLoginUrl = getCurrentEnvLoginUrl(env);
            Map<String, Object> form = new HashMap<>(2);
            form.put("merchant_name", merchantName);
            form.put("merchant_pwd", encryptPwd);
            String body = HttpRequest.post(currentEnvLoginUrl).form(form).contentType(ContentType.FORM_URLENCODED.getValue()).execute().body();
            JSONObject jsonObject = JSONObject.parseObject(body);
            String key = jsonObject.get(CODE).toString();
            if (SUCCESS.equals(key)) {
                String tokenId = String.valueOf(jsonObject.get("token_id"));
                //过期时间  15分钟
                RedisUtil.set(tokeKey, tokenId, 15, TimeUnit.MINUTES);
                systemConfig.setTokenId(tokenId);
            } else {
                if (respCodeMap.containsKey(key)) {
                    throw new BusinessException(respCodeMap.get(key));
                }
                if (i <= RETRY) {
                    //重试
                    getTokenId(systemConfig, ++i);
                }
            }
        } catch (Exception e) {
            log.error("登录冰鉴平台失败", e);
            log.info("登录冰鉴平台失败,账号:{}.密码:{},加密后密码:{},环境:{},登录url:{}", merchantName, merchantPwd, encryptPwd, env, currentEnvLoginUrl);
            throw new BusinessException(e.getMessage());
        }

    }

    /**
     * 请求接口
     *
     * @param apiReqDto    入参
     * @param systemConfig 账号信息
     * @param apiUrl       接口地址
     * @param cls          出参的class
     * @param <T>          出参的class
     * @return T  出参对象
     */
    public static <T> T getApiData(ApiReqDto apiReqDto, SystemConfig systemConfig, String apiUrl, Class<T> cls) {
        //1.获取必要参数
        EnvType env = systemConfig.getEnv();
        String currentEnvApiUrl = getCurrentEnvApiUrl(env, apiUrl);
        //2.加密参数
        Map<String, Object> map = encryptPwdParams(apiReqDto);
        final TimeInterval timeInterval = new TimeInterval();
        String tokenId = systemConfig.getTokenId();
        String body = JSONObject.toJSONString(map);
        //3.1将请求参数信息存储到线程变量
        Map<String, String> header = new HashMap<>(2);
        header.put("token_id", tokenId);
        RecordRequestInfo.recordRequest(currentEnvApiUrl, body, header, "post");
        //3.2发起请求
        String apiData = HttpUtil.createPost(currentEnvApiUrl)
                .header("token_id", tokenId)
                .contentType(ContentType.JSON.getValue())
                .body(body).execute().body();
        log.info("结果返回为,Url:{},params:{},result:{}", apiUrl, JSONObject.toJSONString(apiReqDto), apiData);
        //3.3将三方元数据存储到线程变量
        SystemThreadLocal.set(SystemCons.ORIGIN_DATA, apiData);
        long interval = timeInterval.intervalMs();
        log.info("接口模块请求第三方耗时:{}ms", interval);
        JSONObject jsonObject;
        try {
            jsonObject = JSONObject.parseObject(apiData);
        } catch (Exception e) {
            log.error("数据结构有异常,{}", apiData);
            throw new BusinessException("数据结构有异常");
        }
        String code = jsonObject.getString(CODE);
        if (code.equals(SUCCESS)) {
            return jsonObject.toJavaObject(cls);
        } else {
            String message = respCodeMap.get(code);
            log.error("请求冰鉴结果出错,错误原因:{},授权用户名:{},Url:{},params:{},result:{}", message, systemConfig.getMerchantName(), currentEnvApiUrl, JSONObject.toJSONString(apiReqDto), apiData);
            throw new BusinessException(message);
        }
    }


    /**
     * 加密参数
     * <p>
     * 规则如下:
     * securityType =100
     * 身份证号 id 和手机 号 mobile 参数需为对应的 MD5 编码（32 位 16 进制）,且若身份证号最后 一位为“X”,则 X 必须大写再进行 MD5 编码
     * securityType =101
     * 身份证号 id 和手机号 mobile 参 数需为对应的 SHA256 编码，且若身份证号最后一位为“X”,则 X 必须大写再 进行 SHA256 编码。
     *
     * @return 加密后的入参
     */
    public static Map<String, Object> encryptPwdParams(ApiReqDto apiReqDto) {
        Map<String, Object> map = BeanUtil.beanToMap(apiReqDto);
        String id = String.valueOf(map.get("id"));
        String mobile = String.valueOf(map.get("mobile"));
        //若身份证号最后 一位为“X”,则 X 必须大写再进行 加密
        if ("X".equalsIgnoreCase(id.substring(id.length() - 1))) {
            String lastChar = id.substring(id.length() - 1);
            id = id.substring(0, id.length() - 1) + lastChar.toUpperCase();
        }
        String encryptId = null;
        String encryptMobile = null;
        SecurityType securityType = (SecurityType) map.getOrDefault("securityType", SecurityType.MD5);
        if (securityType.equals(SecurityType.MD5)) {
            log.info("冰鉴请求,加密方式:MD5");
            map.put("security_type", securityType.getValue());
            encryptId = SecureUtil.md5(id);
            encryptMobile = SecureUtil.md5(mobile);
        }
        if (securityType.equals(SecurityType.SHA256)) {
            log.info("冰鉴请求,加密方式:sha256");
            map.put("security_type", securityType.getValue());
            encryptId = SecureUtil.sha256(id);
            encryptMobile = SecureUtil.sha256(mobile);
        }
        map.put("id", encryptId);
        map.put("mobile", encryptMobile);
        return map;
    }
}
